package cn.tradewin.pmp.services;

import java.io.IOException;
import java.io.Reader;

import javax.mail.Session;
import javax.naming.Context;
import javax.naming.InitialContext;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.authc.credential.PasswordMatcher;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.authc.pam.AllSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.tapestry5.ComponentParameterConstants;
import org.apache.tapestry5.MetaDataConstants;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.Translator;
import org.apache.tapestry5.Validator;
import org.apache.tapestry5.beanvalidator.BeanValidatorConfigurer;
import org.apache.tapestry5.beanvalidator.BeanValidatorSource;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Contribute;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.ServiceOverride;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.apache.tapestry5.services.ApplicationGlobals;
import org.apache.tapestry5.services.BaseURLSource;
import org.apache.tapestry5.services.ComponentEventResultProcessor;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestFilter;
import org.apache.tapestry5.services.RequestHandler;
import org.apache.tapestry5.services.Response;
import org.apache.tapestry5.services.compatibility.Compatibility;
import org.apache.tapestry5.services.compatibility.Trait;
import org.apache.tapestry5.upload.services.UploadSymbols;
import org.slf4j.Logger;
import org.tynamo.security.SecuritySymbols;
//import org.tynamo.security.SecuritySymbols;
import org.tynamo.security.services.SecurityFilterChainFactory;
import org.tynamo.security.services.impl.SecurityFilterChain;

import cn.tradewin.common.factories.SelectIdModelFactory;
import cn.tradewin.common.factories.impl.SelectIdModelFactoryImpl;
import cn.tradewin.common.factories.impl.TwdSecurityFilterChainFactoryImpl;
import cn.tradewin.common.realm.TwdJdbcSecurityRealm;
import cn.tradewin.common.realm.TwdRetryLimitSecurityRealm;
import cn.tradewin.common.realm.credentials.RetryLimitHashedCredentialsMatcher;
import cn.tradewin.common.util.DownloadSymbols;
import cn.tradewin.pmp.services.download.OutputStreamResponse;
import cn.tradewin.pmp.services.impl.CustomerManageServiceImpl;
import cn.tradewin.pmp.services.impl.DeptManageServiceImpl;
import cn.tradewin.pmp.services.impl.EmpManageServiceImpl;
import cn.tradewin.pmp.services.impl.Filer;
import cn.tradewin.pmp.services.impl.MasterServiceImpl;
import cn.tradewin.pmp.services.impl.OutputStreamResponseResultProcessor;
import cn.tradewin.pmp.services.impl.PositionManageServiceImpl;
import cn.tradewin.pmp.services.impl.TwdSecurityServiceImpl;
import cn.tradewin.pmp.services.impl.UsersServiceImpl;
import cn.tradewin.pmp.services.mail.MailService;
import cn.tradewin.pmp.services.mail.MailServiceImpl;
import cn.tradewin.pmp.services.security.ContractItemService;
import cn.tradewin.pmp.services.security.ContractService;
import cn.tradewin.pmp.services.security.impl.ContractItemServiceImpl;
import cn.tradewin.pmp.services.security.impl.ContractServiceImpl;
import cn.tradewin.pmp.translators.MoneyTranslator;
import cn.tradewin.pmp.translators.YesNoTranslator;
import cn.tradewin.pmp.validators.Letters;
import freemarker.template.DefaultObjectWrapper;

/**
 * This module is automatically included as part of the Tapestry IoC Registry,
 * it's a good place to configure and extend Tapestry, or to place your own
 * service definitions.
 */
public final class AppModule {

	private final ApplicationGlobals globals;
	private final Logger log;

	public AppModule(ApplicationGlobals globals, Logger log) {
		this.globals = globals;
		this.log = log;
	}

	// public static void
	// contributeApplicationDefaults(MappedConfiguration<String, String>
	// configuration) {
	// configuration.add(ActivitiSymbols.DATABASE_TYPE, "postgres");
	// configuration.add(ActivitiSymbols.JDBC_URL,
	// "jdbc:postgresql://localhost/activiti");
	// configuration.add(ActivitiSymbols.JDBC_DRIVER, "org.postgresql.Driver");
	// configuration.add(ActivitiSymbols.DATABASE_SCHEMA_UPDATE, "true");
	// }

//	public void contributeProcessEngine(
//			Configuration<Resource> deploymentResources) {
//		/**
//		 * Remember: the name of the resource must end with "bpmn20.xml".
//		 * 
//		 * @see: BpmnDeployer.BPMN_RESOURCE_SUFFIX
//		 */
//		deploymentResources.add(new ClasspathResource(
//				"diagrams/VacationProcess.bpmn"));
//	}

	@Scope(ScopeConstants.DEFAULT)
	public MailService buildMailService(
			@InjectService("FreeMarkerService") freemarker.template.Configuration freeMarker) {
		MailService service = null;
		try {
			Context initContext = new InitialContext();
			Context envContext = (Context) initContext.lookup("java:/comp/env");
			Session o = (Session) (envContext.lookup("mail/session"));
			service = new MailServiceImpl(o, freeMarker);
		} catch (Exception e) {
			log.error(e.getMessage());
			throw new RuntimeException(e);
		}
		return service;
	}

	/**
	 * FreeMarkerService configuration
	 * 
	 * @param configuration
	 */
	@Scope(ScopeConstants.DEFAULT)
	public freemarker.template.Configuration buildFreeMarkerService() {
		freemarker.template.Configuration config = null;
		try {
			config = new freemarker.template.Configuration();
			// base directory to find template file
			config.setServletContextForTemplateLoading(
					globals.getServletContext(), "WEB-INF");
			// how to parse data model by template
			config.setObjectWrapper(new DefaultObjectWrapper());
		} catch (Exception e) {
			log.error(e.getMessage());
			throw new RuntimeException(e);
		}
		return config;
	}

	/**
	 * get SqlSessionFactory by mybatis.
	 * 
	 * @param log
	 * @return
	 */
	@Scope(ScopeConstants.DEFAULT)
	public SqlSessionFactory buildSqlSessionFactory() {
		SqlSessionFactory factory = null;
		try {
			String resource = "Configuration.xml";
			Reader reader = Resources.getResourceAsReader(resource);
			factory = new SqlSessionFactoryBuilder().build(reader);
		} catch (Exception e) {
			log.error(e.getMessage());
			throw new RuntimeException(e);
		}
		return factory;
	}

	// /**
	// * get XMemcachedClient
	// * @param log
	// * @return
	// */
	// @Scope(ScopeConstants.DEFAULT)
	// public static MemcachedClient buildMemcachedClient(final Logger log) {
	// MemcachedClient client = XmemcachedUtil.getMemcachedClient();
	// if (client == null) {
	// throw new RuntimeException("can not get memcached client");
	// }
	// return client;
	// }

	// /**
	// * get memcached java client
	// *
	// * @param log
	// * @return
	// */
	// @Scope(ScopeConstants.DEFAULT)
	// public MemCachedClient buildMemCachedClient() {
	// MemCachedClient client = MemcachedUtil.getMemcachedClient();
	// if (client == null) {
	// log.error("can not get memcached client");
	// throw new RuntimeException("can not get memcached client");
	// }
	// return client;
	// }

	/**
	 * used for file upload and download function
	 * 
	 * @param logger
	 * @param uploadsPath
	 * @param fileSizeMax
	 * @return
	 */
	@EagerLoad
	public IFiler buildFiler(
			@Inject @Symbol(UploadSymbols.REPOSITORY_LOCATION) final String uploadsPath,
			@Inject @Symbol(UploadSymbols.FILESIZE_MAX) final long fileSizeMax) {
		return new Filer(log, UploadSymbols.REPOSITORY_LOCATION, uploadsPath,
				UploadSymbols.FILESIZE_MAX, fileSizeMax);
	}

	public static void bind(ServiceBinder binder) {
		// business logical service area begin

		binder.bind(UsersService.class, UsersServiceImpl.class);
		binder.bind(DeptManageService.class, DeptManageServiceImpl.class);
		binder.bind(CustomerManageService.class,CustomerManageServiceImpl.class);
		binder.bind(PositionManageService.class,
				PositionManageServiceImpl.class);
		binder.bind(EmpManageService.class, EmpManageServiceImpl.class);
		binder.bind(ContractService.class, ContractServiceImpl.class);
		binder.bind(ContractItemService.class, ContractItemServiceImpl.class);
		binder.bind(MasterService.class, MasterServiceImpl.class);
		// business logical service area end

		// common service area begin
		binder.bind(SelectIdModelFactory.class, SelectIdModelFactoryImpl.class);
		// common service area end

		// security service area begin 2014/01/03
		binder.bind(TwdSecurityService.class, TwdSecurityServiceImpl.class);

		// shiro using begin
		binder.bind(PasswordService.class, DefaultPasswordService.class);
		binder.bind(CredentialsMatcher.class, PasswordMatcher.class).withId(PasswordMatcher.class.getSimpleName());
		binder.bind(CredentialsMatcher.class, RetryLimitHashedCredentialsMatcher.class).withId(RetryLimitHashedCredentialsMatcher.class.getSimpleName());
		binder.bind(AuthorizingRealm.class, TwdJdbcSecurityRealm.class).withId(TwdJdbcSecurityRealm.class.getSimpleName());
		binder.bind(AuthorizingRealm.class, TwdRetryLimitSecurityRealm.class).withId(TwdRetryLimitSecurityRealm.class.getSimpleName());
		binder.bind(SecurityFilterChainFactory.class,TwdSecurityFilterChainFactoryImpl.class).withId(TwdSecurityFilterChainFactoryImpl.class.getSimpleName());
		// shiro using end
		// security service area end 2014/01/03
	}

	public static void contributeServiceOverride(
			MappedConfiguration<Class<?>, Object> configuration) {
		BaseURLSource source = new BaseURLSource() {
			public String getBaseURL(boolean secure) {
				String protocol = secure ? "https" : "http";
				int port = secure ? 443 : 80;
				return String.format("%s://www.sklmn.org:%d", protocol, port);
			}
		};
		configuration.add(BaseURLSource.class, source);
	}

	public static void contributeFactoryDefaults(
			MappedConfiguration<String, Object> configuration) {
		// The application version number is incorprated into URLs for some
		// assets. Web browsers will cache assets because of the far future
		// expires
		// header. If existing assets are changed, the version number should
		// also
		// change, to force the browser to download new versions. This overrides
		// Tapesty's default
		// (a random hexadecimal number), but may be further overriden by
		// DevelopmentModule or
		// QaModule.
		configuration.override(SymbolConstants.APPLICATION_VERSION,
				"1.0-SNAPSHOT");

	}

	public static void contributeApplicationDefaults(
			MappedConfiguration<String, Object> configuration) {

		// configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en");
		configuration.add(SymbolConstants.PRODUCTION_MODE, false);
		// configuration.add(SymbolConstants.APPLICATION_VERSION,
		// "0.0.1-SNAPSHOT");
		configuration.add(SymbolConstants.SUPPORTED_LOCALES, "ja");
		configuration.add(SymbolConstants.COMBINE_SCRIPTS, false);
		configuration.add(SymbolConstants.MINIFICATION_ENABLED, false);
		configuration.add(SymbolConstants.HMAC_PASSPHRASE, "test");
		configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "jquery");
		// configuration.add(SymbolConstants.BOOTSTRAP_ROOT,
		// "classpath:/META-INF/assets/bootstrap-2.2.2");
		configuration.add(ComponentParameterConstants.GRID_ROWS_PER_PAGE, "15");

		// add for file upload and download begin 2014/01/26
		configuration.add(UploadSymbols.FILESIZE_MAX, 10000000);
		// configuration.add(UploadSymbols.REPOSITORY_LOCATION, "/var/uploads");
		// configuration.add(DownloadSymbols.REPOSITORY_LOCATION,
		// "/var/downloads");
		configuration.add(UploadSymbols.REPOSITORY_LOCATION, "d:/uploads");
		configuration.add(DownloadSymbols.REPOSITORY_LOCATION, "d:/downloads");

		// add for file upload and download end 2014/01/26

		// for security add begin 2014/01/03 shiro
		configuration.add(SymbolConstants.TAPESTRY_VERSION, "5.4-beta-28");
		configuration.add(SecuritySymbols.LOGIN_URL, "/login");
		configuration
				.add(SecuritySymbols.UNAUTHORIZED_URL, "/infra/PageDenied");
		configuration.add(SecuritySymbols.SUCCESS_URL, "/Index");
		configuration.add(SecuritySymbols.REDIRECT_TO_SAVED_URL, "true");
		// for security add end 2014/01/03 shiro
	}

	/**
	 * Adds ComponentEventResultProcessors
	 * 
	 * @param configuration
	 *            the configuration where new ComponentEventResultProcessors are
	 *            registered by the type they are processing
	 * @param response
	 *            the response that the event result processor handles
	 */
	public void contributeComponentEventResultProcessor(
			MappedConfiguration<Class<?>, ComponentEventResultProcessor<?>> configuration,
			Response response) {
		configuration.add(OutputStreamResponse.class,
				new OutputStreamResponseResultProcessor(response));
	}

	@Contribute(BeanValidatorSource.class)
	public static void provideBeanValidatorConfigurer(
			OrderedConfiguration<BeanValidatorConfigurer> configuration) {
		configuration.add("AdvisorConfigurer", new BeanValidatorConfigurer() {
			public void configure(
					javax.validation.Configuration<?> configuration) {
				configuration.ignoreXmlConfiguration();
			}
		});
	}

	@SuppressWarnings("rawtypes")
	public static void contributeTranslatorAlternatesSource(
			MappedConfiguration<String, Translator> configuration,
			ThreadLocale threadLocale) {
		configuration.add("yesno", new YesNoTranslator("yesno"));
		configuration.add("money2", new MoneyTranslator("money2", 2,
				threadLocale));
	}

	@SuppressWarnings("rawtypes")
	public static void contributeFieldValidatorSource(
			MappedConfiguration<String, Validator> configuration) {
		configuration.add("letters", new Letters());
	}

	public void contributeComponentMessagesSource(
			OrderedConfiguration<String> configuration) {
		configuration.add("pmpTranslationMessages",
				"cn/tradewin/pmp/translators/TranslationMessages");
		configuration.add("pmpValidationMessages",
				"cn/tradewin/pmp/validators/ValidationMessages");
	}

	@Contribute(Compatibility.class)
	public static void disableScriptaculous(
			MappedConfiguration<Trait, Boolean> configuration) {
		configuration.add(Trait.INITIALIZERS, false);
		configuration.add(Trait.SCRIPTACULOUS, false);
	}

	// add for security support for ssl begin
	public void contributeMetaDataLocator(
			MappedConfiguration<String, String> configuration) {
		configuration.add("security:" + MetaDataConstants.SECURE_PAGE, "true");
	}

	// add for security support for ssl end

	// add for security by shiro begin
	@Contribute(ServiceOverride.class)
	public static void overrideSecurityFilterChainFactory(
			MappedConfiguration<Class<?>, Object> cfg,
			@Local SecurityFilterChainFactory sfcf) {
		cfg.add(SecurityFilterChainFactory.class, sfcf);
	}

	/**
	 * WebSecurityManager configuration information
	 * 
	 * @param configuration
	 * @param authorizingRealm
	 */
	@Contribute(WebSecurityManager.class)
	public static void addRealms(
			Configuration<Realm> configuration,
			@InjectService("TwdJdbcSecurityRealm") AuthorizingRealm jdbcRealm,
			@InjectService("TwdRetryLimitSecurityRealm") AuthorizingRealm retryLimitRealm,
			@InjectService("ModularRealmAuthenticator") ModularRealmAuthenticator authenticator) {
		authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
		configuration.add(jdbcRealm);
//		configuration.add(retryLimitRealm);

		// ExtendedPropertiesRealm propRealm = new
		// ExtendedPropertiesRealm("classpath:shiro-users.properties");
		// configuration.add(propRealm);
	}

	/**
	 * seucrity test add
	 * 
	 * @param configuration
	 * @param factory
	 */
	public static void contributeSecurityConfiguration(
			Configuration<SecurityFilterChain> configuration,
			SecurityFilterChainFactory factory) {
		// configuration.add(factory.createChain("/authc/signup").add(factory.anon()).build());
		// configuration.add(factory.createChain("/authc/**").add(factory.authc()).build());
		// configuration.add(factory.createChain("/contributed/**").add(factory.authc()).build());
		// configuration.add(factory.createChain("/user/signup").add(factory.anon()).build());
		// configuration.add(factory.createChain("/user/**").add(factory.user()).build());
		// configuration.add(factory.createChain("/perms/view/**").add(factory.perms(),
		// "news:view").build());
		// configuration.add(factory.createChain("/perms/edit/**").add(factory.perms(),
		// "news:edit").build());
		configuration.add(factory.createChain("/security/**")
				.add(factory.roles(), "admin").build());
		// configuration.add(factory.createChain("/test/**").add(factory.authc()).build());
	}

	// add for security by shiro end

	/**
	 * This is a service definition, the service will be named "TimingFilter".
	 * The interface, RequestFilter, is used within the RequestHandler service
	 * pipeline, which is built from the RequestHandler service configuration.
	 * Tapestry IoC is responsible for passing in an appropriate Logger
	 * instance. Requests for static resources are handled at a higher level, so
	 * this filter will only be invoked for Tapestry related requests.
	 * <p/>
	 * <p/>
	 * Service builder methods are useful when the implementation is inline as
	 * an inner class (as here) or require some other kind of special
	 * initialization. In most cases, use the static bind() method instead.
	 * <p/>
	 * <p/>
	 * If this method was named "build", then the service id would be taken from
	 * the service interface and would be "RequestFilter". Since Tapestry
	 * already defines a service named "RequestFilter" we use an explicit
	 * service id that we can reference inside the contribution method.
	 */
	public RequestFilter buildTimingFilter(final Logger log) {
		return new RequestFilter() {
			public boolean service(Request request, Response response,
					RequestHandler handler) throws IOException {
				long startTime = System.currentTimeMillis();

				try {
					// The responsibility of a filter is to invoke the
					// corresponding method
					// in the handler. When you chain multiple filters together,
					// each filter
					// received a handler that is a bridge to the next filter.

					return handler.service(request, response);
				} finally {
					long elapsed = System.currentTimeMillis() - startTime;

					log.info(String.format("Request time: %d ms", elapsed));
				}
			}
		};
	}

	/**
	 * This is a contribution to the RequestHandler service configuration. This
	 * is how we extend Tapestry using the timing filter. A common use for this
	 * kind of filter is transaction management or security. The @Local
	 * annotation selects the desired service by type, but only from the same
	 * module. Without @Local, there would be an error due to the other
	 * service(s) that implement RequestFilter (defined in other modules).
	 */
	public void contributeRequestHandler(
			OrderedConfiguration<RequestFilter> configuration,
			@Local RequestFilter filter) {
		// Each contribution to an ordered configuration has a name, When
		// necessary, you may
		// set constraints to precisely control the invocation order of the
		// contributed filter
		// within the pipeline.

		configuration.add("Timing", filter);
	}

	/***********************************/
	/* Service Advisors begin */
	/* ******************************** */
	// @Match("*")
	// public void adviseNotNull(MethodAdviceReceiver receiver) {
	// MethodAdvice advice = new MethodAdvice() {
	// @Override
	// public void advise(MethodInvocation invocation) {
	// invocation.proceed();
	// if (invocation.getReturnValue() == null) {
	// invocation.setReturnValue("");
	// }
	// }
	// };
	//
	// for (Method m : receiver.getInterface().getMethods())
	// {
	// if (m.getReturnType().equals(String.class))
	// receiver.adviseMethod(m, advice);
	// }
	// receiver.adviseAllMethods(advice);
	// }

}